import os
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'

from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, 
                           QPushButton, QComboBox, QSpinBox, QMessageBox,
                           QProgressBar, QScrollArea, QDoubleSpinBox, QGroupBox, QGridLayout, QFrame)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import torch
import torch.nn as nn
from sklearn.preprocessing import StandardScaler
import pandas as pd
from torch.utils.data import DataLoader, TensorDataset

class MultiHeadSelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads):
        super().__init__()
        self.attn = nn.MultiheadAttention(embed_dim, num_heads, batch_first=True)

    def forward(self, x):
        return self.attn(x, x, x)[0]

class TransformerBlock(nn.Module):
    def __init__(self, embed_dim, num_heads, hidden_dim, dropout=0.1):
        super().__init__()
        self.attn = MultiHeadSelfAttention(embed_dim, num_heads)
        self.norm1 = nn.LayerNorm(embed_dim)
        self.norm2 = nn.LayerNorm(embed_dim)
        self.ff = nn.Sequential(
            nn.Linear(embed_dim, hidden_dim),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim, embed_dim)
        )
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        x = self.norm1(x + self.dropout(self.attn(x)))
        x = self.norm2(x + self.dropout(self.ff(x)))
        return x

class RATransformer(nn.Module):
    def __init__(self, input_dim, embed_dim=16, num_heads=2, num_layers=3, hidden_dim=32, dropout=0.1):
        super().__init__()
        self.input_norm = nn.LayerNorm(input_dim)
        self.embedding = nn.Linear(input_dim, embed_dim)
        self.encoder_layers = nn.Sequential(
            *[TransformerBlock(embed_dim, num_heads, hidden_dim, dropout) for _ in range(num_layers)]
        )
        self.max_pool = nn.AdaptiveMaxPool1d(8)
        self.residual = nn.Linear(8, 8)
        self.decoder_layers = nn.Sequential(
            *[TransformerBlock(8, num_heads, hidden_dim, dropout) for _ in range(2)]
        )
        self.decoder = nn.Sequential(
            nn.Linear(8, input_dim),
            nn.Tanh()
        )

    def forward(self, x):
        x = self.input_norm(x)
        x = self.embedding(x)
        encoded = self.encoder_layers(x)
        pooled = self.max_pool(encoded.unsqueeze(1)).squeeze(1)
        residual_output = self.residual(pooled)
        decoded = self.decoder_layers(pooled + residual_output)
        return self.decoder(decoded)

def analyze_features(data):
    """分析特征方差和相关性"""
    scaler = StandardScaler()
    data_scaled = scaler.fit_transform(data)
    variances = np.var(data_scaled, axis=0)
    feature_names = [f'特征{i + 1}' for i in range(data.shape[1])]
    
    variance_df = pd.DataFrame({
        '特征': feature_names,
        '方差': variances
    }).sort_values('方差', ascending=False)
    
    corr_matrix = np.corrcoef(data_scaled.T)
    high_corr_pairs = []
    for i in range(len(corr_matrix)):
        for j in range(i + 1, len(corr_matrix)):
            if abs(corr_matrix[i, j]) > 0.9:
                high_corr_pairs.append((feature_names[i], feature_names[j], corr_matrix[i, j]))
                
    return variance_df, high_corr_pairs

def select_features(data, variance_percentile=1):
    """特征选择"""
    scaler = StandardScaler()
    data_scaled = scaler.fit_transform(data)
    variances = np.var(data_scaled, axis=0)
    
    # 确保至少保留10%的特征
    min_features = max(1, int(0.1 * data.shape[1]))
    
    # 如果设置的方差百分位数过高导致特征太少，则调整百分位数
    while True:
        variance_threshold = np.percentile(variances, variance_percentile)
        mask = variances > variance_threshold
        if np.sum(mask) >= min_features:
            break
        variance_percentile = max(0, variance_percentile - 1)  # 逐步降低方差百分位数
        if variance_percentile == 0:  # 如果已降到0还不够，就取前N个最高方差的特征
            indices = np.argsort(variances)[::-1]
            mask = np.zeros_like(variances, dtype=bool)
            mask[indices[:min_features]] = True
            break
    
    selected_features = np.where(mask)[0]
    
    # 相关性分析
    corr_matrix = np.corrcoef(data_scaled[:, selected_features].T)
    to_remove = set()
    correlation_counts = np.zeros(len(selected_features))
    
    # 计算每个特征的高相关特征数量
    for i in range(len(corr_matrix)):
        correlation_counts[i] = np.sum(np.abs(corr_matrix[i]) > 0.9) - 1
        
    feature_ranks = np.argsort(correlation_counts)
    kept_features = set()
    
    # 贪心选择特征，确保至少保留最小数量的特征
    for idx in feature_ranks:
        if idx not in to_remove:
            kept_features.add(idx)
            # 只有当剩余特征足够多时才移除高相关特征
            if len(kept_features) > min_features:
                correlated = np.where(np.abs(corr_matrix[idx]) > 0.9)[0]
                to_remove.update(set(correlated) - {idx})
    
    final_features = sorted(list(kept_features))
    final_selected = selected_features[final_features]
    
    # 打印特征选择的详细信息
    print(f"\n特征选择详情:")
    print(f"原始特征数: {data.shape[1]}")
    print(f"方差筛选后: {len(selected_features)}个")
    print(f"相关性筛选后: {len(final_selected)}个")
    print(f"最终保留特征索引: {final_selected.tolist()}")
    
    if len(final_selected) == 0:
        raise ValueError("特征选择后没有保留任何特征，请调整方差百分位数或检查数据。")
    
    return data[:, final_selected], final_selected

class TrainingThread(QThread):
    progress_updated = pyqtSignal(int, float, float)  # 发送进度、训练损失和验证损失
    training_finished = pyqtSignal(list, list)  # 发送训练损失列表和验证损失列表
    error_occurred = pyqtSignal(str)  # 发送错误信息

    def __init__(self, model, train_loader, validation_data, epochs, patience=20):
        super().__init__()
        self.model = model
        self.train_loader = train_loader
        self.validation_data = validation_data
        self.epochs = epochs
        self.patience = patience

    def run(self):
        try:
            optimizer = torch.optim.AdamW(self.model.parameters(), lr=0.001, weight_decay=0.01)
            criterion = nn.MSELoss()
            
            train_losses = []
            validation_losses = []
            best_val_loss = float('inf')
            patience_counter = 0
            
            # 初始化最佳模型状态为当前模型状态
            best_model_state = self.model.state_dict().copy()

            for epoch in range(self.epochs):
                # 训练阶段
                self.model.train()
                total_loss = 0
                for batch in self.train_loader:
                    batch = batch[0]
                    optimizer.zero_grad()
                    output = self.model(batch)
                    loss = criterion(output, batch)
                    loss.backward()
                    torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
                    optimizer.step()
                    total_loss += loss.item()

                avg_train_loss = total_loss / len(self.train_loader)
                train_losses.append(avg_train_loss)

                # 验证阶段
                self.model.eval()
                with torch.no_grad():
                    val_output = self.model(self.validation_data)
                    val_loss = criterion(val_output, self.validation_data)
                    validation_losses.append(val_loss.item())

                # 早停检查
                if val_loss < best_val_loss:
                    best_val_loss = val_loss
                    patience_counter = 0
                    # 深拷贝当前模型状态
                    best_model_state = {k: v.clone() for k, v in self.model.state_dict().items()}
                else:
                    patience_counter += 1

                # 发送进度和损失
                self.progress_updated.emit(epoch + 1, avg_train_loss, val_loss.item())

                # 早停
                if patience_counter >= self.patience:
                    print(f"Early stopping at epoch {epoch}")
                    # 确保best_model_state不为None
                    if best_model_state is not None:
                        self.model.load_state_dict(best_model_state)
                    break

            # 发送完整的损失列表
            self.training_finished.emit(train_losses, validation_losses)

        except Exception as e:
            import traceback
            print(f"训练错误: {str(e)}")
            print(traceback.format_exc())
            self.error_occurred.emit(str(e))

class DLWidget(QWidget):
    def __init__(self, main_window=None):
        super().__init__()
        self.main_window = main_window
        self.data_df = None
        self.file_path = None
        self.model = None
        self.scaler = StandardScaler()
        self.training_thread = None
        self.selected_features = None
        
        # 用于存储训练过程中的损失值
        self.train_losses = []
        self.val_losses = []
        
        self.timer = QTimer()  # 新增定时器
        self.timer.timeout.connect(self.update_display)
        
        self.init_ui()
        
    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(10, 10, 10, 10)
        layout.setSpacing(10)
        
        # 创建主内容区域
        content = QWidget()
        content_layout = QVBoxLayout(content)
        content_layout.setContentsMargins(0, 0, 0, 0)
        content_layout.setSpacing(10)
        
        # 顶部区域：标题和参数设置
        top_widget = QWidget()
        top_layout = QHBoxLayout(top_widget)
        top_layout.setContentsMargins(0, 0, 0, 0)
        
        # 左侧：标题和参数
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        left_layout.setContentsMargins(5, 5, 5, 5)
        left_layout.setSpacing(10)
        
        # 标题
        title = QLabel("深度学习检测")
        title.setStyleSheet("""
            font-size: 24px;
            font-weight: bold;
            color: #2979ff;
            padding: 5px;
            border-bottom: 2px solid #e0e0e0;
        """)
        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        left_layout.addWidget(title)
        
        # 参数设置区域
        param_widget = QWidget()
        param_layout = QHBoxLayout(param_widget)
        param_layout.setContentsMargins(5, 5, 5, 5)
        param_layout.setSpacing(20)
        
        # 添加参数控件 - 只保留训练轮数和批次大小
        self.epochs_spin = QSpinBox()
        self.epochs_spin.setRange(1, 1000)
        self.epochs_spin.setValue(200)
        self.epochs_spin.setPrefix("训练轮数: ")
        self.epochs_spin.setStyleSheet("""
            QSpinBox {
                border: 1px solid #bdbdbd;
                border-radius: 4px;
                padding: 5px;
                background: #f5f5f5;
                min-height: 25px;
                font-size: 14px;
                min-width: 150px;
            }
            QSpinBox:hover {
                border: 1px solid #2979ff;
            }
        """)
        
        self.batch_size_spin = QSpinBox()
        self.batch_size_spin.setRange(1, 128)
        self.batch_size_spin.setValue(16)
        self.batch_size_spin.setPrefix("批次大小: ")
        self.batch_size_spin.setStyleSheet("""
            QSpinBox {
                border: 1px solid #bdbdbd;
                border-radius: 4px;
                padding: 5px;
                background: #f5f5f5;
                min-height: 25px;
                font-size: 14px;
                min-width: 150px;
            }
            QSpinBox:hover {
                border: 1px solid #2979ff;
            }
        """)
        
        # 这两个参数仍然需要保留，但不显示在UI上
        self.patience_spin = QSpinBox()
        self.patience_spin.setRange(1, 100)
        self.patience_spin.setValue(20)
        
        self.variance_spin = QDoubleSpinBox()
        self.variance_spin.setRange(0, 100)
        self.variance_spin.setValue(1)
        
        # 只添加两个参数到布局
        param_layout.addWidget(self.epochs_spin)
        param_layout.addWidget(self.batch_size_spin)
        
        left_layout.addWidget(param_widget)
        
        # 右侧：按钮区域
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)
        right_layout.setContentsMargins(5, 5, 5, 5)
        right_layout.setSpacing(10)
        
        # 添加按钮
        button_widget = QWidget()
        button_layout = QVBoxLayout(button_widget)
        button_layout.setContentsMargins(10, 5, 10, 5)
        button_layout.setSpacing(10)
        
        self.train_button = QPushButton("训练模型")
        self.train_button.setMinimumHeight(35)
        self.train_button.setStyleSheet("""
            QPushButton {
                background-color: #2979ff;
                color: white;
                border: none;
                border-radius: 6px;
                padding: 8px 16px;
                font-size: 16px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #1565c0;
            }
            QPushButton:pressed {
                background-color: #0d47a1;
            }
            QPushButton:disabled {
                background-color: #bbdefb;
                color: #e0e0e0;
            }
        """)
        self.train_button.clicked.connect(self.train_model)
        
        self.predict_button = QPushButton("检测")
        self.predict_button.setMinimumHeight(35)
        self.predict_button.setStyleSheet("""
            QPushButton {
                background-color: #43a047;
                color: white;
                border: none;
                border-radius: 6px;
                padding: 8px 16px;
                font-size: 16px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #2e7d32;
            }
            QPushButton:pressed {
                background-color: #1b5e20;
            }
            QPushButton:disabled {
                background-color: #e8f5e9;
                color: #616161;
                border: 1px solid #a5d6a7;
            }
        """)
        self.predict_button.clicked.connect(self.predict)
        self.predict_button.setEnabled(False)
        
        button_layout.addWidget(self.train_button)
        button_layout.addWidget(self.predict_button)
        right_layout.addWidget(button_widget)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                border: 1px solid #e0e0e0;
                border-radius: 4px;
                text-align: center;
                background-color: #f5f5f5;
                height: 20px;
            }
            QProgressBar::chunk {
                background-color: #2979ff;
                border-radius: 3px;
            }
        """)
        right_layout.addWidget(self.progress_bar)
        
        # 当前损失值显示
        self.loss_label = QLabel()
        self.loss_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.loss_label.setVisible(False)
        self.loss_label.setStyleSheet("""
            font-size: 14px;
            color: #424242;
            padding: 5px;
            background-color: #f5f5f5;
            border-radius: 4px;
        """)
        right_layout.addWidget(self.loss_label)
        
        # 添加左右两侧到顶部布局
        top_layout.addWidget(left_widget, 3)  # 左侧占3份
        top_layout.addWidget(right_widget, 1)  # 右侧占1份
        
        # 添加顶部区域到主布局
        content_layout.addWidget(top_widget)
        
        # 图表区域
        chart_widget = QWidget()
        chart_layout = QVBoxLayout(chart_widget)
        chart_layout.setContentsMargins(5, 5, 5, 5)
        
        # 进一步增大图表尺寸
        self.figure, self.ax = plt.subplots(figsize=(12, 7))
        self.canvas = FigureCanvas(self.figure)
        self.canvas.setMinimumHeight(400)  # 设置最小高度
        
        # 设置图表样式
        plt.rcParams['font.size'] = 12
        plt.rcParams['axes.titlesize'] = 14
        plt.rcParams['axes.labelsize'] = 12
        plt.rcParams['xtick.labelsize'] = 10
        plt.rcParams['ytick.labelsize'] = 10
        plt.rcParams['legend.fontsize'] = 12
        plt.rcParams['figure.titlesize'] = 16
        
        # 设置图表的DPI以提高清晰度
        self.figure.set_dpi(100)
        
        # 调整图表边距
        self.figure.subplots_adjust(left=0.1, right=0.95, top=0.9, bottom=0.1)
        
        chart_layout.addWidget(self.canvas)
        
        # 添加图表区域到主布局
        content_layout.addWidget(chart_widget)
        
        # 添加主内容到布局
        layout.addWidget(content)
        
    def train_model(self):
        if self.data_df is None:
            QMessageBox.warning(self, "警告", "请先加载数据")
            return
            
        try:
            # 清空之前的损失记录
            self.train_losses = []
            self.val_losses = []
            
            # 清空图表
            self.ax.clear()
            self.ax.set_title('等待训练数据...')
            self.canvas.draw()
            
            # 准备数据
            data = self.data_df.values
            
            # 特征选择
            data_selected, self.selected_features = select_features(
                data, 
                variance_percentile=self.variance_spin.value()
            )
            print(f"特征选择后的数据形状: {data_selected.shape}")
            
            # 随机打乱数据
            np.random.seed(42)
            indices = np.random.permutation(len(data_selected))
            
            # 数据划分（80%训练，20%测试）
            train_size = int(0.8 * len(data_selected))
            train_indices = indices[:train_size]
            test_indices = indices[train_size:]
            
            train_data = data_selected[train_indices]
            test_data = data_selected[test_indices]
            
            # 标准化
            self.scaler = StandardScaler()
            train_data_scaled = self.scaler.fit_transform(train_data)
            test_data_scaled = self.scaler.transform(test_data)
            
            # 从训练集中划分验证集（20%）
            val_size = int(0.2 * len(train_data_scaled))
            train_data_final = train_data_scaled[val_size:]
            val_data = train_data_scaled[:val_size]
            
            # 转换为tensor
            train_tensor = torch.tensor(train_data_final, dtype=torch.float32)
            val_tensor = torch.tensor(val_data, dtype=torch.float32)
            self.test_tensor = torch.tensor(test_data_scaled, dtype=torch.float32)
            
            # 创建数据加载器
            batch_size = self.batch_size_spin.value()
            train_loader = DataLoader(
                TensorDataset(train_tensor),
                batch_size=batch_size,
                shuffle=True
            )
            
            # 初始化模型
            input_dim = data_selected.shape[1]
            self.model = RATransformer(input_dim=input_dim, dropout=0.1)
            
            # 获取参数
            epochs = self.epochs_spin.value()
            patience = self.patience_spin.value()
            
            # 禁用训练按钮
            self.train_button.setEnabled(False)
            
            # 显示和重置进度条
            self.progress_bar.setVisible(True)
            self.progress_bar.setMaximum(epochs)
            self.progress_bar.setValue(0)
            
            # 显示损失标签
            self.loss_label.setVisible(True)
            self.loss_label.setText("训练损失: - | 验证损失: -")
            
            # 创建并启动训练线程
            self.training_thread = TrainingThread(
                self.model, train_loader, val_tensor, 
                epochs, patience
            )
            self.training_thread.progress_updated.connect(self.update_training_progress)
            self.training_thread.training_finished.connect(self.training_completed)
            self.training_thread.error_occurred.connect(self.training_error)
            self.training_thread.start()
            
            print("\n数据集统计信息:")
            print(f"训练集: {train_tensor.shape}")
            print(f"验证集: {val_tensor.shape}")
            print(f"测试集: {self.test_tensor.shape}")
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"准备训练过程中出错：{str(e)}")
            self.train_button.setEnabled(True)
            
    def update_training_progress(self, epoch, train_loss, val_loss):
        """更新训练进度和损失显示"""
        self.progress_bar.setValue(epoch)
        self.loss_label.setText(f"训练损失: {train_loss:.6f} | 验证损失: {val_loss:.6f}")
        
        # 存储损失值
        self.train_losses.append(train_loss)
        self.val_losses.append(val_loss)
        
        # 实时更新损失曲线图
        self.ax.clear()
        epochs = range(1, len(self.train_losses) + 1)
        
        # 绘制训练损失
        self.ax.plot(epochs, self.train_losses, 'b-', linewidth=2, label='训练损失')
        
        # 绘制验证损失
        self.ax.plot(epochs, self.val_losses, 'r-', linewidth=2, label='验证损失')
        
        # 设置图表属性
        self.ax.set_title('实时训练进度', fontsize=16, fontweight='bold')
        self.ax.set_xlabel('训练轮次', fontsize=14)
        self.ax.set_ylabel('损失值', fontsize=14)
        self.ax.grid(True, linestyle='--', alpha=0.7)
        self.ax.legend(loc='upper right', fontsize=12)
        
        # 设置Y轴范围，使图表更稳定
        if len(self.train_losses) > 1:
            y_min = min(min(self.train_losses), min(self.val_losses))
            y_max = max(max(self.train_losses[1:]), max(self.val_losses[1:]))  # 跳过第一个值，通常较大
            y_margin = (y_max - y_min) * 0.1
            self.ax.set_ylim([max(0, y_min - y_margin), y_max + y_margin])
        
        # 刷新画布
        self.canvas.draw()
        
    def training_completed(self, train_losses, val_losses):
        """训练完成后的处理"""
        # 重新启用训练按钮
        self.train_button.setEnabled(True)
        
        # 启用预测按钮
        self.predict_button.setEnabled(True)
        
        # 最终更新损失曲线 - 使用完整的损失数据
        self.train_losses = train_losses
        self.val_losses = val_losses
        
        self.ax.clear()
        epochs = range(1, len(train_losses) + 1)
        
        # 绘制训练损失
        self.ax.plot(epochs, train_losses, 'b-', linewidth=2, label='训练损失')
        
        # 绘制验证损失
        self.ax.plot(epochs, val_losses, 'r-', linewidth=2, label='验证损失')
        
        # 设置图表属性
        self.ax.set_title('训练完成', fontsize=16, fontweight='bold')
        self.ax.set_xlabel('训练轮次', fontsize=14)
        self.ax.set_ylabel('损失值', fontsize=14)
        self.ax.grid(True, linestyle='--', alpha=0.7)
        self.ax.legend(loc='upper right', fontsize=12)
        
        # 添加最终损失值标注
        final_train_loss = train_losses[-1]
        final_val_loss = val_losses[-1]
        self.ax.annotate(f'最终训练损失: {final_train_loss:.6f}', 
                         xy=(len(train_losses), final_train_loss),
                         xytext=(len(train_losses)-len(train_losses)*0.3, final_train_loss),
                         arrowprops=dict(facecolor='blue', shrink=0.05, width=1.5, headwidth=8),
                         fontsize=10)
        self.ax.annotate(f'最终验证损失: {final_val_loss:.6f}', 
                         xy=(len(val_losses), final_val_loss),
                         xytext=(len(val_losses)-len(val_losses)*0.3, final_val_loss*1.1),
                         arrowprops=dict(facecolor='red', shrink=0.05, width=1.5, headwidth=8),
                         fontsize=10)
        
        self.canvas.draw()
        
    def training_error(self, error_msg):
        """处理训练过程中的错误"""
        QMessageBox.critical(self, "错误", f"训练过程中出错：{error_msg}")
        self.train_button.setEnabled(True)
        self.progress_bar.setVisible(False)
        self.loss_label.setVisible(False)
            
    def predict(self):
        if self.model is None:
            QMessageBox.warning(self, "警告", "请先训练模型")
            return
            
        try:
            # 使用测试集进行预测
            self.model.eval()
            with torch.no_grad():
                predictions = self.model(self.test_tensor)
                
            # 计算重构误差
            reconstruction_error = torch.mean((predictions - self.test_tensor) ** 2, dim=1).numpy()
            
            # 设置控制限
            threshold = np.percentile(reconstruction_error, 95)
            print(f"\n使用测试集计算的控制限：{threshold:.6f}")
            
            # 分析每个特征的重构误差
            feature_errors = ((predictions - self.test_tensor) ** 2).mean(dim=0).numpy()
            feature_importance = pd.DataFrame({
                '特征索引': self.selected_features,
                '重构误差': feature_errors
            }).sort_values('重构误差', ascending=False)
            
            print("\n特征重构误差分析:")
            print(feature_importance)
            
            # 详细分析误差
            self._analyze_errors(reconstruction_error, threshold, "测试集")
            
            # 修改后的绘图逻辑
            self.ax.clear()
            # 初始化动态绘制元素
            self.line, = self.ax.plot([], [], 'b-', linewidth=1, alpha=0.6, label='重构误差')
            self.scatter = self.ax.scatter([], [], c='b', s=10, alpha=0.6)
            
            # 保存预测结果用于动态显示
            self.reconstruction_errors = reconstruction_error.tolist()
            self.current_index = 0
            
            # 修改后的控制线添加方式（保存为实例变量）
            self.threshold_line = self.ax.axhline(y=threshold, color='r', linestyle='--', 
                                                label=f'控制限 ({threshold:.3f})', linewidth=2)
            self.ax.axhline(y=np.percentile(reconstruction_error, 50), color='g', 
                           linestyle=':', label='中位数线', linewidth=1)
            self.ax.axhline(y=np.percentile(reconstruction_error, 90), color='y', 
                           linestyle=':', label='90%分位线', linewidth=1)
            
            # 设置图表属性
            self.ax.set_title('实时异常检测')
            self.ax.set_xlabel('样本编号')
            self.ax.set_ylabel('重构误差')
            self.ax.legend()
            self.ax.grid(True, alpha=0.3)
            
            # 启动定时器
            self.timer.start(50)  # 每50ms更新一个点
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"预测过程中出错：{str(e)}")
            
    def _analyze_errors(self, errors, threshold, name="数据集"):
        """详细分析重构误差"""
        print(f"\n{name}重构误差分析:")
        print(f"样本数量: {len(errors)}")
        print(f"最小值: {np.min(errors):.6f}")
        print(f"最大值: {np.max(errors):.6f}")
        print(f"平均值: {np.mean(errors):.6f}")
        print(f"中位数: {np.median(errors):.6f}")
        print(f"标准差: {np.std(errors):.6f}")
        
        # 计算各个百分位数
        percentiles = [50, 75, 90, 95, 99]
        for p in percentiles:
            value = np.percentile(errors, p)
            print(f"{p}%分位数: {value:.6f}")
            
        # 统计超限情况
        exceedance = errors > threshold
        exceedance_rate = exceedance.mean() * 100
        print(f"\n超限分析:")
        print(f"超限样本数: {np.sum(exceedance)}")
        print(f"超限比例: {exceedance_rate:.2f}%")
        
        if np.any(exceedance):
            print("\n超限样本的误差值:")
            exceedance_errors = errors[exceedance]
            for i, error in enumerate(exceedance_errors):
                print(f"样本{np.where(exceedance)[0][i]}: {error:.6f}")
                
        # 构建分析结果消息（仅用于控制台输出，不再显示弹窗）
        msg = f"{name}分析结果:\n\n"
        msg += f"样本数量: {len(errors)}\n"
        msg += f"最小值: {np.min(errors):.6f}\n"
        msg += f"最大值: {np.max(errors):.6f}\n"
        msg += f"平均值: {np.mean(errors):.6f}\n"
        msg += f"中位数: {np.median(errors):.6f}\n"
        msg += f"标准差: {np.std(errors):.6f}\n\n"
        
        msg += "分位数分析:\n"
        for p in percentiles:
            value = np.percentile(errors, p)
            msg += f"{p}%分位数: {value:.6f}\n"
            
        msg += f"\n超限分析:\n"
        msg += f"超限样本数: {np.sum(exceedance)}\n"
        msg += f"超限比例: {exceedance_rate:.2f}%"
        
        # 移除弹窗提示
        # QMessageBox.information(self, f"{name}分析结果", msg)

    def update_display(self):
        if self.current_index < len(self.reconstruction_errors):
            # 获取当前点
            x = self.current_index
            y = self.reconstruction_errors[x]
            
            # 更新线条数据
            current_x = list(self.line.get_xdata()) + [x]
            current_y = list(self.line.get_ydata()) + [y]
            self.line.set_data(current_x, current_y)
            
            # 更新散点数据
            offsets = np.array([current_x, current_y]).T
            self.scatter.set_offsets(offsets)
            
            # 自动调整坐标范围
            self.ax.relim()
            self.ax.autoscale_view()
            
            # 修改后的阈值判断
            if y > self.threshold_line.get_ydata()[0]:
                self.ax.plot(x, y, 'rx', markersize=8, zorder=3)  # 确保标记在最上层
            
            # 刷新画布
            self.canvas.draw()
            
            self.current_index += 1
        else:
            self.timer.stop()
            # 移除预测完成的弹窗提示
            # QMessageBox.information(self, "完成", "所有预测结果已显示完毕") 